﻿using System;
using System.Collections.Generic;
using GeoAPI.Geometries;
using NetTopologySuite.Geometries;
using NetTopologySuite.GeometriesGraph;

namespace NetTopologySuite.Operation.Buffer
{
	internal class BufferSubgraph : IComparable
	{
		public BufferSubgraph()
		{
			this._finder = new RightmostEdgeFinder();
		}

		public IList<DirectedEdge> DirectedEdges
		{
			get
			{
				return this._dirEdgeList;
			}
		}

		public IList<Node> Nodes
		{
			get
			{
				return this._nodes;
			}
		}

		public Coordinate RightMostCoordinate
		{
			get
			{
				return this._rightMostCoord;
			}
		}

		public void Create(Node node)
		{
			this.AddReachable(node);
			this._finder.FindEdge(this._dirEdgeList);
			this._rightMostCoord = this._finder.Coordinate;
		}

		private void AddReachable(Node startNode)
		{
			Stack<Node> stack = new Stack<Node>();
			stack.Push(startNode);
			while (stack.Count != 0)
			{
				Node node = stack.Pop();
				this.Add(node, stack);
			}
		}

		private void Add(Node node, Stack<Node> nodeStack)
		{
			node.Visited = true;
			this._nodes.Add(node);
			foreach (EdgeEnd edgeEnd in ((DirectedEdgeStar)node.Edges))
			{
				DirectedEdge directedEdge = (DirectedEdge)edgeEnd;
				this._dirEdgeList.Add(directedEdge);
				Node node2 = directedEdge.Sym.Node;
				if (!node2.IsVisited)
				{
					nodeStack.Push(node2);
				}
			}
		}

		private void ClearVisitedEdges()
		{
			foreach (DirectedEdge directedEdge in this._dirEdgeList)
			{
				directedEdge.Visited = false;
			}
		}

		public void ComputeDepth(int outsideDepth)
		{
			this.ClearVisitedEdges();
			DirectedEdge edge = this._finder.Edge;
			edge.SetEdgeDepths(Positions.Right, outsideDepth);
			BufferSubgraph.CopySymDepths(edge);
			BufferSubgraph.ComputeDepths(edge);
		}

		private static void ComputeDepths(DirectedEdge startEdge)
		{
			HashSet<Node> hashSet = new HashSet<Node>();
			Queue<Node> queue = new Queue<Node>();
			Node node = startEdge.Node;
			queue.Enqueue(node);
			hashSet.Add(node);
			startEdge.Visited = true;
			while (queue.Count != 0)
			{
				Node node2 = queue.Dequeue();
				hashSet.Add(node2);
				BufferSubgraph.ComputeNodeDepth(node2);
				foreach (EdgeEnd edgeEnd in ((DirectedEdgeStar)node2.Edges))
				{
					DirectedEdge sym = ((DirectedEdge)edgeEnd).Sym;
					if (!sym.IsVisited)
					{
						Node node3 = sym.Node;
						if (!hashSet.Contains(node3))
						{
							queue.Enqueue(node3);
							hashSet.Add(node3);
						}
					}
				}
			}
		}

		private static void ComputeNodeDepth(Node n)
		{
			DirectedEdge directedEdge = null;
			foreach (EdgeEnd edgeEnd in ((DirectedEdgeStar)n.Edges))
			{
				DirectedEdge directedEdge2 = (DirectedEdge)edgeEnd;
				if (directedEdge2.IsVisited || directedEdge2.Sym.IsVisited)
				{
					directedEdge = directedEdge2;
					break;
				}
			}
			if (directedEdge == null)
			{
				throw new TopologyException("unable to find edge to compute depths at " + n.Coordinate);
			}
			((DirectedEdgeStar)n.Edges).ComputeDepths(directedEdge);
			foreach (EdgeEnd edgeEnd2 in ((DirectedEdgeStar)n.Edges))
			{
				DirectedEdge directedEdge3 = (DirectedEdge)edgeEnd2;
				directedEdge3.Visited = true;
				BufferSubgraph.CopySymDepths(directedEdge3);
			}
		}

		private static void CopySymDepths(DirectedEdge de)
		{
			DirectedEdge sym = de.Sym;
			sym.SetDepth(Positions.Left, de.GetDepth(Positions.Right));
			sym.SetDepth(Positions.Right, de.GetDepth(Positions.Left));
		}

		public void FindResultEdges()
		{
			foreach (DirectedEdge directedEdge in this._dirEdgeList)
			{
				if (directedEdge.GetDepth(Positions.Right) >= 1 && directedEdge.GetDepth(Positions.Left) <= 0 && !directedEdge.IsInteriorAreaEdge)
				{
					directedEdge.InResult = true;
				}
			}
		}

		public int CompareTo(object o)
		{
			BufferSubgraph bufferSubgraph = (BufferSubgraph)o;
			if (this.RightMostCoordinate.X < bufferSubgraph.RightMostCoordinate.X)
			{
				return -1;
			}
			if (this.RightMostCoordinate.X > bufferSubgraph.RightMostCoordinate.X)
			{
				return 1;
			}
			return 0;
		}

		private readonly RightmostEdgeFinder _finder;

		private readonly List<DirectedEdge> _dirEdgeList = new List<DirectedEdge>();

		private readonly List<Node> _nodes = new List<Node>();

		private Coordinate _rightMostCoord;
	}
}
